﻿//-----------------------------------------------------------------------------------------------------------
// Wbfs Engine (WBFSe) Project by Omega Frost 
// http://wbfse.codeplex.com/
//
// WBFSe is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.IO;
using System.Runtime.InteropServices;
using WBFSe.OS;

namespace WBFSe.IO.Streams
{
    /// <summary>
    /// File stream
    /// </summary>
    public class WbfsFileStream : IWbfsStream, IDisposable
    {
        // Variables

        private Boolean isSparse;
        private IntPtr filePtr;

        // Events

        /// <summary>
        /// Indicates that the stream is closing
        /// </summary>
        public event StreamClosingDelegate Closing;

        // Properties

        /// <summary>
        /// Retrieves the file pointer
        /// </summary>
        public IntPtr FilePtr
        {
            get { return this.filePtr; }
        }

        /// <summary>
        /// Retrieves the stream length
        /// </summary>
        /// <exception cref="System.IOException">On operation failure</exception>
        public long Length
        {
            get
            {
                long size;
                if(!Native.GetFileSizeEx(this.filePtr, out size))
                {
                    int error = Marshal.GetLastWin32Error();
                    throw new WbfsIOException(error);
                }

                return size;
            }
        }

        /// <summary>
        /// Retrieves the physical size
        /// </summary>
        /// <exception cref="System.IOException">On operation failure</exception>
        public long Size
        {
            get
            {
                long size;
                if (!Native.GetSparseSize(this.filePtr, out size))
                {
                    int error = Marshal.GetLastWin32Error();
                    throw new WbfsIOException(error);
                }

                return size;
            }
        }


        // Methods

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="file">Full path</param>
        /// <param name="mode">File mode</param>
        /// <param name="access">File access</param>
        /// <param name="share">File share</param>
        public WbfsFileStream(String file, FileMode mode, FileAccess access, FileShare share)
        {
            this.filePtr = Native.CreateFile(file, access, share,
                IntPtr.Zero, mode, FileAttrib.None, IntPtr.Zero);

            if(this.filePtr == Native.InvalidHandle)
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            if ((mode == FileMode.Create) || (mode == FileMode.CreateNew))
            {
                IWbfsDriveInfo drive = WbfsDevices.GetDrive(file);
                if (drive.IsSparse)
                {
                    if(!Native.SetSparseFile(this.filePtr))
                    {
                        int error = Marshal.GetLastWin32Error();
                        throw new WbfsIOException(error);
                    }
                }
            }
        }

        /// <summary>
        /// Reads data from the stream
        /// </summary>
        /// <param name="pMem">Unmanaged memory</param>
        /// <param name="offset">Offset from pMem</param>
        /// <param name="position">Destination position</param>
        /// <param name="count">Number of bytes to be read</param>
        /// <returns>Number of bytes read</returns>
        /// <exception cref="System.IOException">On operation failure</exception>
        public uint Read(IntPtr pMem, int offset, long position, int count)
        {
            if(!Native.SetFilePointerEx(this.filePtr, position, out position, SeekOrigin.Begin))
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            uint read = (uint)count;
            if (!Native.ReadFile(this.filePtr, IntPtr.Add(pMem, offset), read, out read, IntPtr.Zero))
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            return read;
        }

        /// <summary>
        /// Writes data from the stream
        /// </summary>
        /// <param name="pMem">Unmanaged memory</param>
        /// <param name="offset">Offset from pMem</param>
        /// <param name="position">Destination position</param>
        /// <param name="count">Number of bytes to be written</param>
        /// <returns>Number of bytes written</returns>
        /// <exception cref="System.IOException">On operation failure</exception>
        public uint Write(IntPtr pMem, int offset, long position, int count)
        {
            if (!Native.SetFilePointerEx(this.filePtr, position, out position, SeekOrigin.Begin))
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            uint write = (uint)count;
            if (!Native.WriteFile(this.filePtr, IntPtr.Add(pMem, offset), write, out write, IntPtr.Zero))
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            return write;
        }

        /// <summary>
        /// Sets a region as empty
        /// </summary>
        /// <param name="position">Destination position</param>
        /// <param name="count">Number of bytes to set</param>
        /// <returns>Number of bytes set</returns>
        /// <exception cref="System.IOException">On operation failure</exception>
        public void WriteEmpty(long position, int count)
        {
            if (!Native.SetSparseRegion(this.filePtr, position, count))
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }
        }

        /// <summary>
        /// Closes the stream, this method can fail silently
        /// </summary>
        public void Close()
        {
            if (this.Closing != null)
                this.Closing(this);

            Native.CloseHandle(this.filePtr);
            this.filePtr = Native.InvalidHandle;
        }

        /// <summary>
        /// Disposes the stream
        /// </summary>
        public void Dispose()
        {
            this.Close();
        }
    }
}
